package seg;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import seg.appx.EllipseFit;
import seg.appx.Fit;
import seg.appx.Primitive;
import seg.cd.CornerDetector;
import doc.SampledPoint;
import doc.Stroke;

public class RecurSeg {

	public void selectCD(CornerDetector.TYPE type) {
		setCd(CornerDetector.getCD(type, getK(), getRhos()));
	}

	private static RecurSeg instance;

	public static RecurSeg get() {
		if (instance == null) {
			instance = new RecurSeg();
		}
		return instance;
	}

	private SampledPoint[] points;

	private double[] rhos;

	private int K;

	private List<Integer> candidates;

	private CornerDetector cd;

	public CornerDetector getCd() {
		return cd;
	}

	public void setCd(CornerDetector cd) {
		this.cd = cd;
	}

	public SampledPoint[] getPoints() {
		if (points == null)
			points = new SampledPoint[0];
		return points;
	}

	public void setPoints(SampledPoint[] points) {
		clear();
		this.points = points;
	}

	public int getK() {
		return K;
	}

	public void setK(int k) {
		clear();
		K = k;
	}

	public List<Integer> getCandidates() {
		if (candidates == null)
			candidates = new ArrayList<Integer>();
		return candidates;
	}

	public void setCandidates(List<Integer> candidates) {
		this.candidates = candidates;
	}

	private void clear() {
		candidates = null;
	}

	public int[] getCorners() {
		Collections.sort(getCandidates());
		int[] out = new int[getCandidates().size()];
		int i = 0;
		Iterator<Integer> it = getCandidates().iterator();
		while (it.hasNext()) {
			out[i++] = it.next();
		}
		return out;
	}

	public double[] getRhos() {
		return rhos;
	}

	public void setRhos(double[] rhos) {
		this.rhos = rhos;
	}

	public void setPoints(List<SampledPoint> ps) {
//		System.out.println(ps);
		points = new SampledPoint[ps.size()];
		ps.toArray(points);
	}

	public void segment() {
		setRhos(RelativeRho.calcRhos(getPoints(), getK()));
//		DoubleStat.print(getRhos());
		System.out.println("=== start recursive segmentation ===");
		Stroke.get().clearFits();
		segment(0, getRhos().length, 0);
	}

	private void segment(int s, int e, int depth) {
//		if (depth > 1)
//			return;

		for (int i = 0; i < depth; i++) {
			if (i < depth - 1)
				System.out.print("  ");
			else
				System.out.print("|-");
		}
		System.out.print("seg: " + s + " - " + e);

		Fit fit = Primitive.fit(getPoints(), s, e);
		String type = "null";
		if (fit.isLine()) {
			type = "line";
			System.out.println(" ------------ is " + type);
			Stroke.get().appendFit(fit);
			return;
		}
		if (fit.isEllipse()) {
			type = "ellipse";
			System.out.println(" ------------ is " + type);
			Stroke.get().appendFit(fit);
			return;
		}

		CornerDetector cd = getCd();
		cd.setInterval(s, e);
		cd.setRhos(getRhos());
		cd.setK(getK());

		cd.filtrate();
		int[] index = cd.getIndex();

		if (index.length == 0) {
			System.out.println(" ------------- no feature points found");
//			segment(s, e, depth+1);
			Stroke.get().appendFit(EllipseFit.fit(getPoints(), s, e));
			return;
		}

		index = mergeNeighbor(index);
		if (index.length == 0) {
			System.out.println();
			return;
		}

		System.out.println(" get " + index.length + " corners");
		addCandidates(index);

		int prev = s;
		for (int i = 0; i < index.length; i++) {
			segment(prev, index[i], depth+1);
			prev = index[i];
		}
		segment(prev, e, depth+1);
	}

	private int[] mergeNeighbor(int[] index) {
		List<Integer> ints = new ArrayList<Integer>();

		int GAP = 2;
		boolean isSeg = false;
		int prev = -1;
//		ints.add(prev);
		for (int i = 0; i < index.length - 1; i++) {
			if (index[i+1] - index[i] < GAP) {
				isSeg = true;
				prev = index[i];
			} else {
				if (!isSeg) {
					ints.add(index[i]);
				} else {
					isSeg = false;
					ints.add(findMaxRhoIndexBetween(prev, index[i]+1));
				}
			}
		}
		if (isSeg) {
			ints.add(findMaxRhoIndexBetween(prev, index[index.length-1]+1));
		} else {
			ints.add(index[index.length-1]);
		}

		int newints[] = new int[ints.size()];
		int i = 0;
		for (Iterator<Integer> it = ints.iterator(); it.hasNext(); ) {
			newints[i++] = it.next();
		}
		return newints;
	}

	private int findMaxRhoIndexBetween(int s, int e) {
		int index = s;
		for (int i = s + 1; i < e; i++) {
			if (getRhos()[index] < getRhos()[i])
				index = i;
		}
		return index;
	}

	public void addCandidates(int[] index) {
		for (int i : index) {
			getCandidates().add(i);
			updateRhoAround(i);
		}
	}

	public void updateRhoAround(int index) {
		for (int i = index - K; i <= index + K; i++) {
			if (i >= 0 && i < getRhos().length)
				getRhos()[i] = 0;
		}
	}
}
